<!--
 下拉选择组件
 @Author: mosowe
 @Date:2023-01-30 08:48:23
-->

<template>
  <view class="zo-select-tabs-component">
    <!-- 筛选列表 -->
    <view
      :class="{
        'select-tabs-menu': true,
        'scroll-x': props.scrollX && !props.col,
        'col-tabs': props.col
      }">
      <view
        :class="{
          'select-tabs-item': true,
          current: clickIndex === index
        }"
        v-for="(item, index) in props.list"
        :key="index"
        @click="clickItem(item, index)">
        <!-- 标题栏 -->
        <!-- 使用picker组件的 -->
        <template v-if="item.type === 'picker'">
          <picker
            @columnchange="columnchange($event, item, index)"
            @cancel="pickerCancel"
            @change="change($event, item)"
            :range="item.options?.range"
            :range-key="item.options['range-key']"
            :start="item.options?.start || '00:00'"
            :end="item.options?.end || '23:59'"
            :fields="item.options?.fields || 'day'"
            :mode="item.options?.mode || 'selector'"
            :value="choosedData[item.key]?.index"
            :disabled="!!item.disabled">
            <Item
              :disabled="!!item.disabled"
              :col="props.col"
              :select="choosedData[item.key] && choosedData[item.key]?.label"
              :selectColor="props.selectColor || '#333333'"
              :title="item.title"
              :value="choosedData[item.key] && choosedData[item.key]?.label"
              :placeholder="item.placeholder"
              :icon="
                (choosedData[item.key] && choosedData[item.key]?.value) || clickIndex === index
                  ? item.selectIcon || item.icon
                  : item.icon
              "
              :slotName="item?.slot"
              :scrollX="props.scrollX">
              <!-- #ifndef MP -->
              <template
                v-if="instance?.slots[item?.slot]"
                v-slot:[item.slot]>
                <slot
                  :name="item.slot"
                  :item="item"></slot>
              </template>
              <!-- #endif -->

              <!-- #ifdef MP -->
              <template
                slot="{{item.slot}}"
                v-if="item.slot">
                <slot
                  :name="item.slot"
                  :item="item"></slot>
              </template>
              <!-- #endif -->
            </Item>
          </picker>
        </template>
        <!-- 使用日历组件的，基于uni-datetime-picker组件 -->
        <template v-else-if="item.type === 'calendar'">
          <uni-datetime-picker
            @change="change($event, item)"
            @maskClick="pickerCancel"
            v-bind="item.options"
            :modelValue="choosedData[item.key]?.index"
            :disabled="!!item.disabled">
            <Item
              :disabled="!!item.disabled"
              :col="props.col"
              :select="choosedData[item.key] && choosedData[item.key]?.label"
              :selectColor="props.selectColor || ' #333333'"
              :title="item.title"
              :value="choosedData[item.key] && choosedData[item.key]?.label"
              :placeholder="item.placeholder"
              :icon="
                (choosedData[item.key] && choosedData[item.key]?.value) || clickIndex === index
                  ? item.selectIcon || item.icon
                  : item.icon
              "
              :slotName="item?.slot"
              :scrollX="props.scrollX">
              <!-- #ifndef MP -->
              <template
                v-if="instance?.slots[item?.slot]"
                v-slot:[item.slot]>
                <slot
                  :name="item.slot"
                  :item="item"></slot>
              </template>
              <!-- #endif -->

              <!-- #ifdef MP -->
              <template
                slot="{{item.slot}}"
                v-if="item.slot">
                <slot
                  :name="item.slot"
                  :item="item"></slot>
              </template>
              <!-- #endif -->
            </Item>
          </uni-datetime-picker>
        </template>
        <!-- 使用级联组件的，基于uni-data-picker组件，不能懒加载 -->
        <template v-else-if="item.type === 'cascade'">
          <uni-data-picker
            v-slot:default="{ data, error, options }"
            @change="change($event, item)"
            @nodeclick="nodeclick($event, item, index)"
            @popupclosed="pickerCancel"
            v-bind="item.options"
            :modelValue="choosedData[item.key]?.index"
            :readonly="!!item.disabled">
            <Item
              :disabled="!!item.disabled"
              :col="props.col"
              :select="choosedData[item.key] && choosedData[item.key]?.label"
              :selectColor="props.selectColor || ' #333333'"
              :title="item.title"
              :value="choosedData[item.key] && choosedData[item.key]?.label"
              :placeholder="item.placeholder"
              :icon="
                (choosedData[item.key] && choosedData[item.key]?.value) || clickIndex === index
                  ? item.selectIcon || item.icon
                  : item.icon
              "
              :slotName="item?.slot"
              :scrollX="props.scrollX">
              <!-- #ifndef MP -->
              <template
                v-if="instance?.slots[item?.slot]"
                v-slot:[item.slot]>
                <slot
                  :name="item.slot"
                  :item="item"></slot>
              </template>
              <!-- #endif -->

              <!-- #ifdef MP -->
              <template
                slot="{{item.slot}}"
                v-if="item.slot">
                <slot
                  :name="item.slot"
                  :item="item"></slot>
              </template>
              <!-- #endif -->
            </Item>
          </uni-data-picker>
        </template>
        <!-- 多选组件+other，基于uni-popup组件 -->
        <template v-else>
          <Item
            :disabled="!!item.disabled"
            :col="props.col"
            :select="choosedData[item.key] && choosedData[item.key]?.label"
            :selectColor="props.selectColor || ' #333333'"
            :title="item.title"
            :value="choosedData[item.key] && choosedData[item.key]?.label"
            :placeholder="item.placeholder"
            :icon="
              (choosedData[item.key] && choosedData[item.key]?.value) || clickIndex === index
                ? item.selectIcon || item.icon
                : item.icon
            "
            :slotName="item?.slot"
            :scrollX="props.scrollX">
            <!-- #ifndef MP -->
            <template
              v-if="instance?.slots[item?.slot]"
              v-slot:[item.slot]>
              <slot
                :name="item.slot"
                :item="item"></slot>
            </template>
            <!-- #endif -->

            <!-- #ifdef MP -->
            <template
              slot="{{item.slot}}"
              v-if="item.slot">
              <slot
                :name="item.slot"
                :item="item"></slot>
            </template>
            <!-- #endif -->
          </Item>
        </template>
      </view>
    </view>
    <!-- 多选弹框 -->
    <uni-popup
      ref="multipleRef"
      type="bottom"
      @maskClick="multipleCancel">
      <view class="multiple-popup-body">
        <view class="multiple-popup-title">
          <text>{{ multipleTitle }}</text>
          <view
            class="icon-close"
            @click="multipleCancel">
            取消
          </view>
        </view>
        <view class="multiple-popup-scroll-box">
          <view
            class="multiple-popup-item"
            v-for="(item, index) in multipleList"
            :key="index"
            @click="multipleChoose(item, index)">
            <text class="name">{{ item[multipleRangeLabel] }}</text>

            <uni-icons
              type="checkmarkempty"
              size="20"
              color="#409eff"
              v-show="multipleActive.includes(index)"></uni-icons>
          </view>
        </view>
        <view class="multiple-popup-btns">
          <view
            class="btn-item cancel"
            @click="multipleReset">
            重置
          </view>
          <view
            class="btn-item submit"
            @click="multipleSubmit">
            确定
          </view>
        </view>
      </view>
    </uni-popup>
  </view>
</template>

<script setup lang="ts">
import { getCurrentInstance, ref, watchEffect } from 'vue';
import Item from './item.vue';
// picker模式下，普通选择器+多列选择器不支持纯字符串/数字数组，即每一项必须是object类型

/*
 * 树结构转一维数组
 * @param nodes 要转的树数据
 * @param childKey 子集字段，可选，默认leaf
 */
const ZoTreeToArray = (nodes: any, childKey = 'leaf'): any => {
  var r = [];
  if (Array.isArray(nodes)) {
    for (var i = 0, l = nodes.length; i < l; i++) {
      r.push(nodes[i]); // 取每项数据放入一个新数组
      if (Array.isArray(nodes[i][childKey]) && nodes[i][childKey].length > 0)
        // 若存在leaf则递归调用，把数据拼接到新数组中，并且删除该leaf
        r = r.concat(ZoTreeToArray(nodes[i][childKey]));
      delete nodes[i][childKey];
    }
  }
  return r;
};

// props定义
interface options {
  range?: any[]; // picker，multiple的列表选项
  'range-key'?: string; //  picker，multiple的展示项键，默认label
  mode?: 'selector' | 'multiSelector' | 'time' | 'date'; // picker的mode属性
  type?: 'date' | 'daterange' | 'datetime' | 'datetimerange'; // calendar日历的type属性
  localdata?: any; // cascade级联的树形数据列表，本地数据，没有懒加载功能
  map?: { text: string; value: string }; // 级联组件的map属性
  [key: string]: any;
}
interface listItem {
  key: string; // 希望返回的数据中的键名，【唯一】
  type: 'picker' | 'cascade' | 'multiple' | 'calendar' | 'other'; // 类型
  title: string; // 默认文案
  icon?: string; // 右侧图标，字体/本地/网络
  selectIcon?: string; // 点击/选中时图标，字体/本地/网络，类型需和icon一致
  multiplePopupTitle?: string; // multiple多选时，弹框的顶部标题
  selectKey?: string; // picker，multiple， 查询key，即需要获取的值的key，默认id
  defaultSelect?: any; // 默认值，undefind,null,''表示没有初始值，有些组件默认值是字符串/数字，有的是数组，根据实际情况写
  treeChidKey?: string; // cascade级联树结构子集键名称
  placeholder?: string; // col垂直排列时右侧默认提示语
  disabled?: boolean; // 是否禁止选择,col垂直排列有效
  slot?: string; // 该项插槽名
  options: options; // 组件相关的其他配置，透传对应的组件props
}
interface propsType {
  list: listItem[]; // 配置项
  col?: boolean; // 垂直排列
  scrollX?: boolean; // 可水平滚动
  selectColor?: string; // 选择后的颜色
}
const props = defineProps<propsType>();

const instance = getCurrentInstance();

// emits定义
interface emitsType {
  (e: 'change', data: any, key: string): void; // 组件数据改变时
  (e: 'columnchange', data: any): void; // 多列picker列数据改变事件
  (e: 'click', data: any): void; // other类型点击事件
  (e: 'nodeclick', data: any): void; // 级联组件节点点击事件
}
const emits = defineEmits<emitsType>();

let isColumnChange = false; // 多列选择器列触发时状态，props.list有修改，禁止初始化
let isOtherChange = false; // 点击type === other选项时，watchEffect只修改other
let isNodeclick = false; // type === cascade时，节点点击事件， props.list有修改，禁止初始化
// 点击项
const clickIndex = ref<number>(-1);
// 已选择数据
// choosedData一级key为对应的搜索字段，label为展示内容，index为索引，data为全部属性
let initData: any = null; // 保存最原始的初始化数据
let initTimer: any = null;
const choosedData = ref<any>({});
const watchData = ref<any>({});
const synchro = () => {
  choosedData.value = { ...choosedData.value, ...watchData.value };
};
// 初始化，默认值赋值
watchEffect(() => {
  if (!isColumnChange && !isNodeclick) {
    isColumnChange = false;
    isNodeclick = false;
    const obj: any = {};
    props.list.forEach((item: listItem) => {
      if (!isOtherChange) {
        // 单选，picker选择器
        if (item.type === 'picker') {
          const rangeKey = item.options?.['range-key'] || 'label'; // 显示的文案key
          // 普通picker选择器
          if (!item.options?.mode || (item.options?.mode === 'selector' && item.options?.range)) {
            const defaultData = item.options?.range?.filter(
              (fItem: any) =>
                fItem[item.selectKey || 'id'] === item?.defaultSelect &&
                !['', null, undefined].includes(item?.defaultSelect)
            )[0];
            obj[item.key] = {
              label: (defaultData && defaultData[rangeKey]) || '',
              value: defaultData || '',
              index:
                item.options?.range?.findIndex(
                  (fItem: any) => fItem[item.selectKey || 'id'] === item?.defaultSelect
                ) || -1
            };
          }
          // 多列picker选择器
          if (item.options?.mode === 'multiSelector' && item.options?.range && !isColumnChange) {
            isColumnChange = false;
            const indexArr: number[] = [];
            item.options?.range?.forEach((fItem, index) => {
              fItem?.forEach((sItem: any, sIndex: number) => {
                if (item?.defaultSelect?.includes(sItem[item.selectKey])) {
                  indexArr.push(sIndex);
                }
              });
            });

            const arr: any[] = [];
            indexArr?.forEach((fItem: number, index: number) => {
              arr.push(item.options?.range && item.options?.range[index][fItem]);
            });
            const label = arr
              .filter((item) => item[rangeKey] !== '请选择')
              .map((item) => item[rangeKey])
              .join(',');
            obj[item.key] = {
              label: label,
              value: label ? arr : '',
              index: indexArr
            };
          }
          // 时间picker选择器
          if (item.options?.mode === 'time') {
            obj[item.key] = {
              label: item?.defaultSelect,
              value: item?.defaultSelect,
              index: item?.defaultSelect
            };
          }
          // 日期picker选择器
          if (item.options?.mode === 'date') {
            obj[item.key] = {
              label: item?.defaultSelect,
              value: item?.defaultSelect,
              index: item?.defaultSelect
            };
          }
        }

        // 日历类型
        if (item.type === 'calendar') {
          obj[item.key] = {
            label: Array.isArray(item?.defaultSelect)
              ? item?.defaultSelect?.join('至')
              : item?.defaultSelect,
            value: item?.defaultSelect,
            index: item?.defaultSelect
          };
        }

        // 级联类型
        if (item.type === 'cascade') {
          const text = item.options?.map?.text || 'text';
          const value = item.options?.map?.value || 'value';
          const arr = JSON.parse(JSON.stringify(item.options.localdata));
          const label = ZoTreeToArray(arr, item.treeChidKey || 'children').filter(
            (fitem: any) => fitem[value] === item?.defaultSelect
          )[0];
          obj[item.key] = {
            label: label ? label[text] : item?.defaultSelect,
            value: item?.defaultSelect,
            index: item?.defaultSelect
          };
        }

        // 多选
        if (item.type === 'multiple') {
          const rangeKey = item.options?.['range-key'] || 'label'; // 显示的文案key
          const selectKey = item.selectKey || 'id';
          const i: number[] = [];
          const cItems: any[] = [];
          item.options.range?.forEach((fitem: any, index: number) => {
            if (item?.defaultSelect?.includes(fitem[selectKey])) {
              i.push(index);
              cItems.push(fitem);
            }
          });
          obj[item.key] = {
            label: cItems.map((item: any) => item[rangeKey]).join(','),
            value: cItems,
            index: i
          };
        }
      }

      // 其他不存在的类型，一般可用作自定义
      if (item.type === 'other') {
        obj[item.key] = {
          label: item?.defaultSelect,
          value: item?.defaultSelect,
          index: item?.defaultSelect
        };
      }
    });

    // choosedData.value = { ...choosedData.value, ...obj };
    watchData.value = { ...watchData.value, ...obj };
    if (!initData) {
      initData = JSON.parse(JSON.stringify(obj));
    }
    isOtherChange = false;
    synchro();
    if (initTimer) {
      clearTimeout(initTimer);
      initTimer = null;
    }
    initTimer = setTimeout(() => {
      clearTimeout(initTimer);
      initTimer = null;
      emits('change', watchData.value, 'init');
    }, 200);
  }
});

// 点击栏目
// 多选时数据列表
const multipleTitle = ref('请选择');
const multipleList = ref<any>([]);
const multipleRangeLabel = ref<string>('label');
const multipleSelectKey = ref<string>('id');
const multipleItem = ref<listItem | null>(null);
const multipleRef = ref<any>(null);
const multipleActive = ref<number[]>([]);
const multipleActiveItem = ref<any[]>([]);
const clickItem = (item: listItem, index: number) => {
  if (item.disabled) {
    return;
  }
  emits('click', { item, index });
  if (item.type === 'other') {
    isOtherChange = true;
  }
  // 多选栏目点击
  if (item.type === 'multiple') {
    multipleTitle.value = item?.multiplePopupTitle || '请选择';
    multipleList.value = item.options.range;
    multipleRangeLabel.value = item.options?.['range-key'] || 'label';
    multipleSelectKey.value = item?.selectKey || 'id';
    multipleItem.value = item;

    multipleActive.value = [...choosedData.value[item.key]?.index] || [];
    multipleActiveItem.value = [...choosedData.value[item.key]?.value] || [];

    multipleRef.value.open();
  }
  if (clickIndex.value === index) {
    clickIndex.value = -1;
    return;
  }
  clickIndex.value = index;
};
// 栏目弹框取消
const pickerCancel = () => {
  clickIndex.value = -1;
};
// 数据改变提交，数据传回父组件
const change = (e: any, item: listItem) => {
  //
  clickIndex.value = -1;
  // 值设置
  // picker选择器
  if (item.type === 'picker') {
    const rangeKey = item.options?.['range-key'] || 'label'; // 显示的文案key
    // 普通picker选择器
    if ((!item.options?.mode || item.options?.mode === 'selector') && item.options?.range) {
      if (item.options?.range[e.detail.value][rangeKey] !== '请选择') {
        choosedData.value[item.key] = {
          label: item.options?.range[e.detail.value][rangeKey],
          value: item.options?.range[e.detail.value],
          index: e.detail.value || -1
        };
      } else {
        choosedData.value[item.key] = {
          label: '',
          value: '',
          index: -1
        };
      }
    }
    // 多列picker选择器
    if (item.options?.mode === 'multiSelector' && item.options?.range) {
      isColumnChange = false;
      const arr: any[] = [];

      e.detail.value?.forEach((fItem: number, index: number) => {
        arr.push(item.options?.range && item.options?.range[index][fItem]);
      });
      const label = arr
        .filter((fitem) => fitem[rangeKey] !== '请选择')
        .map((mitem) => mitem[rangeKey])
        .join(',');
      choosedData.value[item.key] = {
        label: label,
        value: label ? arr : '',
        index: e.detail.value
      };
    }
    // 时间picker选择器
    if (item.options?.mode === 'time') {
      choosedData.value[item.key] = {
        label: e.detail.value,
        value: e.detail.value,
        index: e.detail.value
      };
    }
    // 日期picker选择器
    if (item.options?.mode === 'date') {
      choosedData.value[item.key] = {
        label: e.detail.value,
        value: e.detail.value,
        index: e.detail.value
      };
    }
  }
  // 日历类型
  if (item.type === 'calendar') {
    choosedData.value[item.key] = {
      label: Array.isArray(e) ? e.join('至') : e,
      value: e,
      index: e
    };
  }
  // 级联类型
  if (item.type === 'cascade') {
    choosedData.value[item.key] = {
      label: e.detail.value.reverse()[0].text,
      value: e.detail.value.reverse()[0].value,
      index: e.detail.value.reverse()[0].value
    };
  }
  // 多选
  if (item.type === 'multiple') {
    const rangeKey = item.options?.['range-key'] || 'label'; // 显示的文案key
    choosedData.value[item.key] = {
      label: [...multipleActiveItem.value].map((item) => item[rangeKey]).join(','),
      value: [...multipleActiveItem.value],
      index: [...e]
    };
  }

  emits('change', choosedData.value, item.key);
};
// 多列选择器下某一列改变时触发
const columnchange = (e: any, item: listItem, index: number) => {
  isColumnChange = true;
  emits('columnchange', { e, item, index });
};
// 级联组件节点点击事件
const nodeclick = (e: any, item: listItem, index: number) => {
  isNodeclick = true;
  emits('nodeclick', { e, item, index });
};
// 多选,选项点击
const multipleChoose = (item: any, index: number) => {
  if (multipleActive.value.includes(index)) {
    const i = multipleActive.value.findIndex((item) => item === index);
    multipleActive.value.splice(i, 1);
    multipleActiveItem.value.splice(i, 1);
  } else {
    multipleActive.value.push(index);
    multipleActiveItem.value.push(item);
  }
};
// 多选-提交
const multipleSubmit = () => {
  change([...multipleActive.value], multipleItem.value as listItem);
  multipleCancel();
};
// 多选-重置
const multipleReset = () => {
  const item = multipleItem.value as listItem;
  // choosedData.value[item.key] = { ...initData[item.key] };

  multipleActive.value = [...initData[item.key]?.index] || [];
  multipleActiveItem.value = [...initData[item.key]?.value] || [];
};
// 多选-取消
const multipleCancel = () => {
  multipleTitle.value = '请选择';
  multipleList.value = [];
  multipleActive.value = [];
  multipleActiveItem.value = [];
  multipleRangeLabel.value = 'label';
  multipleSelectKey.value = 'id';
  clickIndex.value = -1;
  multipleRef.value.close();
};
</script>
<style lang="scss" scoped>
.zo-select-tabs-component {
  width: 100%;
  overflow: hidden;
  background-color: #ffffff;
  box-shadow: 0 3rpx 5rpx rgba(0, 0, 0, 0.05);
  .select-tabs-menu {
    overflow: hidden;
    height: 88rpx;
    width: 100%;
    display: flex;
    .select-tabs-item {
      flex: 1;
    }
    &.scroll-x {
      overflow-x: auto;
      white-space: nowrap;
      .select-tabs-item {
        flex: 0 0 23vw;
        padding: 0 10rpx;
      }
    }
    &.col-tabs {
      height: auto;
      display: block;
      .select-tabs-item {
        width: 100%;
      }
    }
  }

  .multiple-popup-body {
    background-color: #fff;
    overflow: hidden;
    transform: translateY(env(safe-area-inset-bottom));
    transform: translateY(constant(safe-area-inset-bottom));
    .multiple-popup-title {
      text-align: center;
      line-height: 100rpx;
      height: 100rpx;
      color: #333;
      border-bottom: 1rpx solid #999;
      position: relative;
      .icon-close {
        position: absolute;
        right: 30rpx;
        top: 50%;
        transform: translateY(-50%);
        font-size: 28rpx;
        color: #999;
      }
    }
    .multiple-popup-scroll-box {
      max-height: 60vh;
      overflow-y: auto;
      padding: 0 30rpx;
      line-height: 80rpx;
      font-size: 28rpx;
      color: #666666;
      .multiple-popup-item {
        display: flex;
        height: 80rpx;
        overflow: hidden;
        justify-content: space-between;
        border-bottom: 1rpx solid #f7f7f7;
      }
    }
    .multiple-popup-btns {
      padding: 30rpx;
      margin-bottom: env(safe-area-inset-bottom);
      margin-bottom: constant(safe-area-inset-bottom);
      display: flex;
      .btn-item {
        flex: 1;
        justify-content: space-between;
        border-radius: 50rpx;
        height: 80rpx;
        text-align: center;
        line-height: 80rpx;
        font-size: 26rpx;
        &.cancel {
          background-color: #f7f7f7;
          color: #333;
        }
        &.submit {
          background-color: #37c880;
          color: #fff;
        }
        + .btn-item {
          margin-left: 30rpx;
        }
      }
    }
  }
}
</style>
